﻿/**
 * MetroFramework - Modern UI for WinForms
 * 
 * The MIT License (MIT)
 * Copyright (c) 2011 Sven Walter, http://github.com/viperneo
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of 
 * this software and associated documentation files (the "Software"), to deal in the 
 * Software without restriction, including without limitation the rights to use, copy, 
 * modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, 
 * and to permit persons to whom the Software is furnished to do so, subject to the 
 * following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in 
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

namespace Creek.UI.Metro.Controls
{
    using System;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;

    using Creek.UI.EFML.Base;
    using Creek.UI.EFML.Base.Controls;
    using Creek.UI.Metro.Components;
    using Creek.UI.Metro.Drawing;
    using Creek.UI.Metro.Interfaces;

    using MetroFramework;

    [ToolboxBitmap(typeof(ComboBox))]
    public class MetroComboBox : ComboBox, IMetroControl, IUIElement
    {
        #region Interface

        private MetroColorStyle metroStyle = MetroColorStyle.Default;

        private MetroThemeStyle metroTheme = MetroThemeStyle.Default;

        private bool useCustomBackColor;

        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public event EventHandler<MetroPaintEventArgs> CustomPaintBackground;

        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public event EventHandler<MetroPaintEventArgs> CustomPaint;

        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public event EventHandler<MetroPaintEventArgs> CustomPaintForeground;

        [Category(MetroDefaults.PropertyCategory.Appearance)]
        [DefaultValue(MetroColorStyle.Default)]
        public MetroColorStyle Style
        {
            get
            {
                if (this.DesignMode || this.metroStyle != MetroColorStyle.Default)
                {
                    return this.metroStyle;
                }

                if (this.StyleManager != null && this.metroStyle == MetroColorStyle.Default)
                {
                    return this.StyleManager.Style;
                }
                if (this.StyleManager == null && this.metroStyle == MetroColorStyle.Default)
                {
                    return MetroDefaults.Style;
                }

                return this.metroStyle;
            }
            set
            {
                this.metroStyle = value;
            }
        }

        [Category(MetroDefaults.PropertyCategory.Appearance)]
        [DefaultValue(MetroThemeStyle.Default)]
        public MetroThemeStyle Theme
        {
            get
            {
                if (this.DesignMode || this.metroTheme != MetroThemeStyle.Default)
                {
                    return this.metroTheme;
                }

                if (this.StyleManager != null && this.metroTheme == MetroThemeStyle.Default)
                {
                    return this.StyleManager.Theme;
                }
                if (this.StyleManager == null && this.metroTheme == MetroThemeStyle.Default)
                {
                    return MetroDefaults.Theme;
                }

                return this.metroTheme;
            }
            set
            {
                this.metroTheme = value;
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public MetroStyleManager StyleManager { get; set; }

        [DefaultValue(false)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public bool UseCustomBackColor
        {
            get
            {
                return this.useCustomBackColor;
            }
            set
            {
                this.useCustomBackColor = value;
            }
        }

        [DefaultValue(false)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public bool UseCustomForeColor { get; set; }

        [DefaultValue(false)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public bool UseStyleColors { get; set; }

        [Browsable(false)]
        [Category(MetroDefaults.PropertyCategory.Behaviour)]
        [DefaultValue(false)]
        public bool UseSelectable
        {
            get
            {
                return this.GetStyle(ControlStyles.Selectable);
            }
            set
            {
                this.SetStyle(ControlStyles.Selectable, value);
            }
        }

        protected virtual void OnCustomPaintBackground(MetroPaintEventArgs e)
        {
            if (this.GetStyle(ControlStyles.UserPaint) && this.CustomPaintBackground != null)
            {
                this.CustomPaintBackground(this, e);
            }
        }

        protected virtual void OnCustomPaint(MetroPaintEventArgs e)
        {
            if (this.GetStyle(ControlStyles.UserPaint) && this.CustomPaint != null)
            {
                this.CustomPaint(this, e);
            }
        }

        protected virtual void OnCustomPaintForeground(MetroPaintEventArgs e)
        {
            if (this.GetStyle(ControlStyles.UserPaint) && this.CustomPaintForeground != null)
            {
                this.CustomPaintForeground(this, e);
            }
        }

        #endregion

        #region Fields

        private bool displayFocusRectangle;

        private bool drawPrompt;

        private bool isFocused;

        private bool isHovered;

        private bool isPressed;

        private MetroComboBoxSize metroComboBoxSize = MetroComboBoxSize.Medium;

        private MetroComboBoxWeight metroComboBoxWeight = MetroComboBoxWeight.Regular;

        private string promptText = "";

        [DefaultValue(false)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public bool DisplayFocus
        {
            get
            {
                return this.displayFocusRectangle;
            }
            set
            {
                this.displayFocusRectangle = value;
            }
        }

        [DefaultValue(DrawMode.OwnerDrawFixed)]
        [Browsable(false)]
        public new DrawMode DrawMode
        {
            get
            {
                return DrawMode.OwnerDrawFixed;
            }
            set
            {
                base.DrawMode = DrawMode.OwnerDrawFixed;
            }
        }

        [DefaultValue(ComboBoxStyle.DropDownList)]
        [Browsable(false)]
        public new ComboBoxStyle DropDownStyle
        {
            get
            {
                return ComboBoxStyle.DropDownList;
            }
            set
            {
                base.DropDownStyle = ComboBoxStyle.DropDownList;
            }
        }

        [DefaultValue(MetroComboBoxSize.Medium)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public MetroComboBoxSize FontSize
        {
            get
            {
                return this.metroComboBoxSize;
            }
            set
            {
                this.metroComboBoxSize = value;
            }
        }

        [DefaultValue(MetroComboBoxWeight.Regular)]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public MetroComboBoxWeight FontWeight
        {
            get
            {
                return this.metroComboBoxWeight;
            }
            set
            {
                this.metroComboBoxWeight = value;
            }
        }

        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        [DefaultValue("")]
        [Category(MetroDefaults.PropertyCategory.Appearance)]
        public string PromptText
        {
            get
            {
                return this.promptText;
            }
            set
            {
                this.promptText = value.Trim();
                this.Invalidate();
            }
        }

        [Browsable(false)]
        public override Font Font
        {
            get
            {
                return base.Font;
            }
            set
            {
                base.Font = value;
            }
        }

        #endregion

        #region Constructor

        public MetroComboBox()
        {
            this.SetStyle(
                ControlStyles.SupportsTransparentBackColor | ControlStyles.OptimizedDoubleBuffer
                | ControlStyles.ResizeRedraw | ControlStyles.UserPaint,
                true);

            base.DrawMode = DrawMode.OwnerDrawFixed;
            base.DropDownStyle = ComboBoxStyle.DropDownList;

            this.drawPrompt = (this.SelectedIndex == -1);
        }

        #endregion

        #region Paint Methods

        protected override void OnPaintBackground(PaintEventArgs e)
        {
            try
            {
                Color backColor = this.BackColor;

                if (!this.useCustomBackColor)
                {
                    backColor = MetroPaint.BackColor.Form(this.Theme);
                }

                if (backColor.A == 255 && this.BackgroundImage == null)
                {
                    e.Graphics.Clear(backColor);
                    return;
                }

                base.OnPaintBackground(e);

                this.OnCustomPaintBackground(new MetroPaintEventArgs(backColor, Color.Empty, e.Graphics));
            }
            catch
            {
                this.Invalidate();
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            try
            {
                if (this.GetStyle(ControlStyles.AllPaintingInWmPaint))
                {
                    this.OnPaintBackground(e);
                }

                this.OnCustomPaint(new MetroPaintEventArgs(Color.Empty, Color.Empty, e.Graphics));
                this.OnPaintForeground(e);
            }
            catch
            {
                this.Invalidate();
            }
        }

        protected virtual void OnPaintForeground(PaintEventArgs e)
        {
            base.ItemHeight = this.GetPreferredSize(Size.Empty).Height;

            Color borderColor, foreColor;

            if (this.isHovered && !this.isPressed && this.Enabled)
            {
                foreColor = MetroPaint.ForeColor.ComboBox.Hover(this.Theme);
                borderColor = MetroPaint.BorderColor.ComboBox.Hover(this.Theme);
            }
            else if (this.isHovered && this.isPressed && this.Enabled)
            {
                foreColor = MetroPaint.ForeColor.ComboBox.Press(this.Theme);
                borderColor = MetroPaint.BorderColor.ComboBox.Press(this.Theme);
            }
            else if (!this.Enabled)
            {
                foreColor = MetroPaint.ForeColor.ComboBox.Disabled(this.Theme);
                borderColor = MetroPaint.BorderColor.ComboBox.Disabled(this.Theme);
            }
            else
            {
                foreColor = MetroPaint.ForeColor.ComboBox.Normal(this.Theme);
                borderColor = MetroPaint.BorderColor.ComboBox.Normal(this.Theme);
            }

            using (var p = new Pen(borderColor))
            {
                var boxRect = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
                e.Graphics.DrawRectangle(p, boxRect);
            }

            using (var b = new SolidBrush(foreColor))
            {
                e.Graphics.FillPolygon(
                    b,
                    new[]
                        {
                            new Point(this.Width - 20, (this.Height / 2) - 2),
                            new Point(this.Width - 9, (this.Height / 2) - 2),
                            new Point(this.Width - 15, (this.Height / 2) + 4)
                        });
            }

            var textRect = new Rectangle(2, 2, this.Width - 20, this.Height - 4);

            TextRenderer.DrawText(
                e.Graphics,
                this.Text,
                MetroFonts.ComboBox(this.metroComboBoxSize, this.metroComboBoxWeight),
                textRect,
                foreColor,
                TextFormatFlags.Left | TextFormatFlags.VerticalCenter);

            this.OnCustomPaintForeground(new MetroPaintEventArgs(Color.Empty, foreColor, e.Graphics));

            if (this.displayFocusRectangle && this.isFocused)
            {
                ControlPaint.DrawFocusRectangle(e.Graphics, this.ClientRectangle);
            }

            if (this.drawPrompt)
            {
                this.DrawTextPrompt(e.Graphics);
            }
        }

        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            if (e.Index >= 0)
            {
                Color foreColor;
                Color backColor = this.BackColor;

                if (!this.useCustomBackColor)
                {
                    backColor = MetroPaint.BackColor.Form(this.Theme);
                }

                if (e.State == (DrawItemState.NoAccelerator | DrawItemState.NoFocusRect)
                    || e.State == DrawItemState.None)
                {
                    using (var b = new SolidBrush(backColor))
                    {
                        e.Graphics.FillRectangle(
                            b, new Rectangle(e.Bounds.Left, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height));
                    }

                    foreColor = MetroPaint.ForeColor.Link.Normal(this.Theme);
                }
                else
                {
                    using (var b = new SolidBrush(MetroPaint.GetStyleColor(this.Style)))
                    {
                        e.Graphics.FillRectangle(
                            b, new Rectangle(e.Bounds.Left, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height));
                    }

                    foreColor = MetroPaint.ForeColor.Tile.Normal(this.Theme);
                }

                var textRect = new Rectangle(0, e.Bounds.Top, e.Bounds.Width, e.Bounds.Height);
                TextRenderer.DrawText(
                    e.Graphics,
                    this.GetItemText(this.Items[e.Index]),
                    MetroFonts.ComboBox(this.metroComboBoxSize, this.metroComboBoxWeight),
                    textRect,
                    foreColor,
                    TextFormatFlags.Left | TextFormatFlags.VerticalCenter);
            }
            else
            {
                base.OnDrawItem(e);
            }
        }

        private void DrawTextPrompt()
        {
            using (Graphics graphics = this.CreateGraphics())
            {
                this.DrawTextPrompt(graphics);
            }
        }

        private void DrawTextPrompt(Graphics g)
        {
            Color backColor = this.BackColor;

            if (!this.useCustomBackColor)
            {
                backColor = MetroPaint.BackColor.Form(this.Theme);
            }

            var textRect = new Rectangle(2, 2, this.Width - 20, this.Height - 4);
            TextRenderer.DrawText(
                g,
                this.promptText,
                MetroFonts.ComboBox(this.metroComboBoxSize, this.metroComboBoxWeight),
                textRect,
                SystemColors.GrayText,
                backColor,
                TextFormatFlags.Left | TextFormatFlags.VerticalCenter | TextFormatFlags.EndEllipsis);
        }

        #endregion

        #region Focus Methods

        protected override void OnGotFocus(EventArgs e)
        {
            this.isFocused = true;
            this.isHovered = true;
            this.Invalidate();

            base.OnGotFocus(e);
        }

        protected override void OnLostFocus(EventArgs e)
        {
            this.isFocused = false;
            this.isHovered = false;
            this.isPressed = false;
            this.Invalidate();

            base.OnLostFocus(e);
        }

        protected override void OnEnter(EventArgs e)
        {
            this.isFocused = true;
            this.isHovered = true;
            this.Invalidate();

            base.OnEnter(e);
        }

        protected override void OnLeave(EventArgs e)
        {
            this.isFocused = false;
            this.isHovered = false;
            this.isPressed = false;
            this.Invalidate();

            base.OnLeave(e);
        }

        #endregion

        #region Keyboard Methods

        protected override void OnKeyDown(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space)
            {
                this.isHovered = true;
                this.isPressed = true;
                this.Invalidate();
            }

            base.OnKeyDown(e);
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            //isHovered = false;
            //isPressed = false;
            this.Invalidate();

            base.OnKeyUp(e);
        }

        #endregion

        #region Mouse Methods

        protected override void OnMouseEnter(EventArgs e)
        {
            this.isHovered = true;
            this.Invalidate();

            base.OnMouseEnter(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                this.isPressed = true;
                this.Invalidate();
            }

            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            this.isPressed = false;
            this.Invalidate();

            base.OnMouseUp(e);
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            //This will check if control got the focus
            //If not thats the only it will remove the focus color
            if (!this.isFocused)
            {
                this.isHovered = false;
            }

            this.Invalidate();

            base.OnMouseLeave(e);
        }

        #endregion

        #region Overridden Methods

        private const int OCM_COMMAND = 0x2111;

        private const int WM_PAINT = 15;

        public override Size GetPreferredSize(Size proposedSize)
        {
            Size preferredSize;
            base.GetPreferredSize(proposedSize);

            using (Graphics g = this.CreateGraphics())
            {
                string measureText = this.Text.Length > 0 ? this.Text : "MeasureText";
                proposedSize = new Size(int.MaxValue, int.MaxValue);
                preferredSize = TextRenderer.MeasureText(
                    g,
                    measureText,
                    MetroFonts.ComboBox(this.metroComboBoxSize, this.metroComboBoxWeight),
                    proposedSize,
                    TextFormatFlags.Left | TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter);
                preferredSize.Height += 4;
            }

            return preferredSize;
        }

        protected override void OnSelectedIndexChanged(EventArgs e)
        {
            base.OnSelectedIndexChanged(e);
            this.drawPrompt = (this.SelectedIndex == -1);
            this.Invalidate();
        }

        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);

            if (((m.Msg == WM_PAINT) || (m.Msg == OCM_COMMAND)) && (this.drawPrompt))
            {
                this.DrawTextPrompt();
            }
        }

        #endregion

        #region Implementation of IUIElement

        public string Content
        {
            get
            {
                return this.Text;
            }
            set
            {
                this.Text = value;
            }
        }

        public string ID
        {
            get
            {
                return this.Name;
            }
            set
            {
                this.Name = value;
            }
        }

        public IValidator Validator { get; set; }

        public IStyle style
        {
            get
            {
                return new ControlStyle(this);
            }
        }

        #endregion
    }
}